#include "zookeeperutil.h"
#include "mprpcapplication.h" // 要知道zkeeper服务器的ip port

#include <iostream>

// 全局的watcher观察器: zkserver 给 zkclient的通知
void global_watcher(struct _zhandle *zh, int type, int state, const char *path, void *watcherCtx)
{
    if (type == ZOO_SESSION_EVENT) // 表示会话级别的事件类型，如连接状态的变化（例如连接丢失、重新连接成功或会话过期）
    {
        if (state == ZOO_CONNECTED_STATE) // ZooKeeper客户端与服务器之间的连接状态 ZOO_CONNECTED_STATE 表示已连接，ZOO_EXPIRED_SESSION_STATE 表示会话过期等。
        {
            // 连接zkclient 和 zkserver连接成功， 获取自定义结构： 这里是信号量
            sem_t *sem = (sem_t *)zoo_get_context(zh);
            // 增加信号量的计数值
            sem_post(sem);
        }
    }
}

ZkClient::ZkClient() : m_zhandle(nullptr)
{
}

ZkClient::~ZkClient()
{
    if (m_zhandle != nullptr)
    {
        zookeeper_close(m_zhandle); // 关闭并释放一个 ZooKeeper 句柄
    }
}

void ZkClient::Start()
{
    std::string host = MprpcApplication::GetInstance().GetConig().load("zookeeperip");
    std::string port = MprpcApplication::GetInstance().GetConig().load("zookeeperport");
    std::string conn_str = host + ":" + port;

    // 初始化一个新的 ZooKeeper 句柄。
    //  服务器地址列表的字符串，格式为 "hostname:port"。可以指定多个服务器地址，使用逗号分隔。
    // zookeeper_init()是异步调用会在 （API调用）当前线程 创建一个线程发送起 连接zookeeper的(网络IO)线程，
    // 当服务端真实接收到后会通过fn回调通知给客户端这里
    // watcher回调线程也是第三个独立线程
    m_zhandle = zookeeper_init(conn_str.c_str(), global_watcher, 30000, nullptr, nullptr, 0);
    if (nullptr == m_zhandle)
    {
        std::cout << "zookeeper_init error!" << std::endl;
        exit(EXIT_FAILURE);
    }

    // 创建信号
    sem_t sem;
    sem_init(&sem, 0, 0);
    // 当监视器回调函数被触发时，我们可以从回调函数中访问这个上下文，从而获取我们在其中存储的信息
    // 通过zoo_set_context()绑定了回调和信号量
    zoo_set_context(m_zhandle, &sem);

    // 主线程进行等待一个 信号量: 等于zkclient 与 zkserver事件类型是ZOO_SESSION_EVENT 并且连接状态是ZOO_CONNECTED_STATE
    sem_wait(&sem);
    std::cout << "zookeeper_init success!" << std::endl;
}

// 用于创建znode节点: 每一个节点都有一个路径， 数据， 数据长度， 状态表示是否是永久性节点
void ZkClient::Create(const char *path, const char *data, int datalen, int state)
{
    char path_buffer[128];
    int bufferlen = sizeof(path_buffer);
    int flag; // 成功时返回 ZOK，表示 znode 存在。如果 znode 不存在，则返回 ZNONODE。
    // 先判断path表示的znode节点是否存在， 如果存在， 就不再重复创建
    flag = zoo_exists(m_zhandle, path, 0, NULL);
    if (flag == ZNONODE)
    { // 表示path的znode节点不存在
        // 创建指定path的znode节点
        flag = zoo_create(m_zhandle, path, data, datalen, &ZOO_OPEN_ACL_UNSAFE, state, path_buffer, bufferlen);
        if (flag == ZOK)
        {
            std::cout << "znode create success! path:" << path << std::endl;
        }
        else
        {
            std::cout << "flag:" << flag << std::endl;
            std::cout << "znode create error! path:" << path << std::endl;
            exit(EXIT_FAILURE);
        }
    }
}

// 根据指定的path获取znode节点的值
std::string ZkClient::GetData(const char *path)
{
    char buffer[64];
    int bufferlen = sizeof(buffer);
    int flag = zoo_get(m_zhandle, path, 0, buffer, &bufferlen, nullptr);
    if (flag != ZOK)
    {
        std::cout << "get znode error! path:" << path << std::endl;
        return "";
    }
    else 
    {
        return buffer;
    }
}
